#region OpenNETCF Copyright Information
/*
Copyright (c) 2007 OpenNETCF Consulting, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial 
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using OpenNETCF.WindowsMobile.Ink.Interfaces;
using OpenNETCF.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Drawing;
using System.IO;

namespace OpenNETCF.WindowsMobile.Ink
{
    /// <summary>
    /// Base class to use when using the IInkOverlay COM object within a custom control
    /// </summary>
    public abstract class InkControlBase : Control
    {
        /// <summary>
        /// BorderStyle of the control
        /// </summary>
        private BorderStyle m_borderStyle = BorderStyle.FixedSingle;

        /// <summary>
        /// Main IInkOverlay COM object
        /// </summary>
        private Ink m_inkOverlay;

        /// <summary>
        /// Gets or sets the borderstyle of the control
        /// </summary>
        public BorderStyle BorderStyle
        {
            get
            {
                return m_borderStyle;
            }
            set
            {
                if (m_borderStyle != value)
                {
                    m_borderStyle = value;
                    this.Invalidate();
                }
            }
        }

        /// <summary>
        /// Clears the InkOverlay control of all ink strokes.
        /// </summary>
        public virtual void Clear()
        {
            //Before setting a new IInkDisp object you must disable the IInkOverlay object then re-enable it
            m_inkOverlay.IInkOverlay.Enabled = false;
            m_inkOverlay.IInkOverlay.Ink = InkDisp.CreateInstance();
            m_inkOverlay.IInkOverlay.Enabled = true;
        }

        /// <summary>
        /// Opens a Ink Serialized Format (ISF) file.
        /// </summary>
        /// <param name="file">The Ink Serialized Format (ISF) file to open.</param>
        public virtual void Open(string file)
        {
            //Read the file as a stream
            FileStream stm = File.OpenRead(file);
            byte[] data = new byte[(int)stm.Length];
            stm.Read(data, 0, data.Length);
            stm.Close();

            //Clear the current ink strokes
            Clear();

            //Load the data
            m_inkOverlay.IInkOverlay.Ink.Load(data);
        }

        /// <summary>
        /// Saves the current ink strokes to a file using the appropriate InkPersistenceFormat
        /// </summary>
        /// <param name="filename"></param>
        public virtual void Save(string filename, InkPersistenceFormat format)
        {
            //Disable the IInkOverlay
            m_inkOverlay.IInkOverlay.Enabled = false;

            //Save the strokes using the provided format.  The return is a byte[].
            object oData = m_inkOverlay.IInkOverlay.Ink.Save(format, InkPersistenceCompressionMode.IPCM_Default);

            //Save the byte array
            byte[] data = null;
            if(oData.GetType().Equals(typeof(byte[])))
                data = (byte[])oData;
            else if(oData.GetType().Equals(typeof(String)))
            {
                data = Encoding.ASCII.GetBytes((string)oData);
            }
            FileStream stm = File.OpenWrite(filename);
            stm.Write(data, 0, data.Length);
            stm.Close();

            //Enable the IInkOverlay
            m_inkOverlay.IInkOverlay.Enabled = true;
        }

        /// <summary>
        /// Saves the current strokes using the Ink Serialized Format (ISF).
        /// </summary>
        /// <param name="filename">The name of the file to save to.</param>
        public virtual void Save(string filename)
        {
            Save(filename, InkPersistenceFormat.IPF_InkSerializedFormat);
        }

        /// <summary>
        /// Saves the current strokes using the Ink Serialized Format (ISF).
        /// </summary>
        /// <param name="filename">The name of the file to save to.</param>
        /// <param name="properties">Any extended property items to save within the Ink Serialized Format using <see cref="IInkExtendedProperties"/>.</param>
        public virtual void Save(string filename, List<ExtendedProperty> properties)
        {
            
            //Save the data
            Save(filename);
        }

        protected void Save(string filename, List<ExtendedProperty> props, InkPersistenceFormat format)
        {
            //Save the extended properties
            for (int x = 0; x < props.Count; x++)
                m_inkOverlay.ExtendedProperties.Add(new Guid(props[x].Id), props[x].Data);


            Save(filename, format);
        }

        /// <summary>
        /// Retreives an extended property associated with the IInkDisp COM object
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [Obsolete("Use InkControlBase.Ink.ExtendedProperties instead.",true)]
        public virtual object GetExtendedProperty(string id)
        {
            return Ink.ExtendedProperties[new Guid(id)];
        }

        /// <summary>
        /// Gets the InkOverlay associacted with the control.
        /// </summary>
        /// <remarks>Added to be inline with the desktop</remarks>
        public Ink Ink
        {
            get
            {
                return m_inkOverlay;
            }
        }
        /// <summary>
        /// Gets the internal handle to associate the IInkOverlay object with
        /// </summary>
        internal virtual IntPtr HandleInternal
        {
            get
            {
                return this.Handle;
            }
        }

        internal virtual void OnMouseUpInternal(MouseEventArgs e)
        {
            //HACK When using the IInkOverlay object in managed code seems that the control Capture property is set to true.  When trying to 
            //click outside the bounds of the control (ie a button)
            this.BoundingControl.Capture = false;
            Debug.WriteLine(string.Format("Mouse out of bounds removing capture. Focused={0}, Pt={1},{2}", this.BoundingControl.Focused, Control.MousePosition.X, Control.MousePosition.Y));
            SendTap(Control.MousePosition.X, Control.MousePosition.Y);
        }
        
        internal virtual void OnMouseDownInternal(MouseEventArgs e)
        {
            this.BoundingControl.Focus();
        }

        internal virtual Control BoundingControl
        {
            get
            {
                return this;
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (this.ClientRectangle.Contains(e.X, e.Y))
                OnMouseDownInternal(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (!this.ClientRectangle.Contains(e.X, e.Y))
                OnMouseUpInternal(e);
            
            base.OnMouseDown(e);
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (StaticMethods.IsRunTime)
            {
                if (m_inkOverlay != null)
                {
                    IInkRectangle rc = m_inkOverlay.IInkOverlay.GetWindowInputRectangle();
                    rc.SetRectangle(0, 0, this.Height, this.Width);
                    m_inkOverlay.IInkOverlay.SetWindowInputRectangle(rc);
                    Marshal.ReleaseComObject(rc);

                }
            }
        }

        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            if (StaticMethods.IsRunTime)
            {
                if (m_inkOverlay == null)
                {
                    //Create the IInkOverlay object
                    m_inkOverlay = new Ink();

                    //Listen for all stroke events
                    m_inkOverlay.IInkOverlay.SetEventInterest(InkCollectorEventInterest.Stroke, true);

                    //We need to set the handle (or control) the inkoverlay object is bound to
                    m_inkOverlay.IInkOverlay.hWnd = this.HandleInternal;
                    m_inkOverlay.IInkOverlay.Enabled = true;
                }
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            //Draw a border
            if (m_borderStyle != BorderStyle.None)
            {
                using (Pen p = new Pen(Color.Black))
                    pe.Graphics.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
            }

            base.OnPaint(pe);
        }

        protected override void Dispose(bool disposing)
        {
            //Dispose of the InkOverlay control
            if (m_inkOverlay != null)
            {
                if (disposing)
                {
                    m_inkOverlay.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        internal void SendTap(int x, int y)
        {
            NativeMethods.mouse_event(NativeMethods.MOUSEEVENTF.LEFTDOWN | NativeMethods.MOUSEEVENTF.ABSOLUTE, (int)((65535 / Screen.PrimaryScreen.Bounds.Width) * x), (int)((65535 / Screen.PrimaryScreen.Bounds.Height) * y), 0, 0);
            NativeMethods.mouse_event(NativeMethods.MOUSEEVENTF.LEFTUP, 0, 0, 0, 0);
        }

        #region WndProc for DEBUG build
#if DEBUG1
        IntPtr m_defaultWndProc = IntPtr.Zero;
        NativeMethods.WndProcDelegate m_wndProc;

        protected override void OnParentChanged(EventArgs e)
        {
            if (StaticMethods.IsRunTime)
            {
                if (this.Handle != IntPtr.Zero)
                {
                    this.m_defaultWndProc = NativeMethods.GetWindowLong(this.Handle, GWL.WNDPROC);
                    m_wndProc = new NativeMethods.WndProcDelegate(Callback);
                    NativeMethods.SetWindowLong(this.Handle, GWL.WNDPROC, m_wndProc);
                }
            }

            base.OnParentChanged(e);
        }

        private IntPtr Callback(IntPtr hWnd, uint msg, IntPtr wparam, IntPtr lparam)
        {
            Message message = Message.Create(hWnd, (int)msg, wparam, lparam);
            this.WndProc(ref message);
            return message.Result;
        }

        protected virtual void WndProc(ref Message m)
        {
            WM msg = (WM)m.Msg;

            Debug.WriteLine(this.GetType().ToString() + " - " + msg.ToString());
            if (WM.CAPTURECHANGED == msg)
            {
                Debug.WriteLine(this.GetType().ToString() + " - " +msg.ToString() + " w:" + m.WParam.ToString() + " l:" + m.LParam.ToString());
            }
            DefWndProc(ref m);
        }

        public void DefWndProc(ref Message m)
        {
            if (this.m_defaultWndProc == IntPtr.Zero)
            {
                m.Result = NativeMethods.DefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam);
            }
            else
            {
                m.Result = NativeMethods.CallWindowProc(this.m_defaultWndProc, m.HWnd, (uint)m.Msg, m.WParam, m.LParam);
            }
        }
#endif
        #endregion

    }
}
